package main

import (
	"crypto/sha256"
	"encoding/hex"
	"encoding/json"
	"fmt"
	"io"
	"log"
	"net/http"
	"os"
	"strconv"

	"github.com/davecgh/go-spew/spew"
	"github.com/gorilla/mux"
	"github.com/joho/godotenv"

	"strings"
	"sync"
	"time"
)

// 定义挖矿难度
const difficult = 1

// 定义区块
type Block struct {
	Index     int    //记录数据
	Timestamp string //写入数据的时间
	BPM       int    //每分钟跳动的次数
	Hash      string //当前区块的hash值
	PrevHash  string //上一区块的hash值
	Difficult int    //当前区块的难度
	Nonce     string //挖矿中符合条件的数字（nonce值）
}

// 声明存放区块的集合（切片）
var Blockchain []Block

// 使用Post传递区块数据
type Mwssage struct {
	BPM int
}

// 防止同一时间产生多个区块（区块冲突）（互斥锁）
var mutex = &sync.Mutex{}

// 计算区块的hash值
func calculateHash(block Block) string {
	//拼接区块数据
	blockData := strconv.Itoa(block.Index) + block.Timestamp + strconv.Itoa(block.BPM) + block.PrevHash + strconv.Itoa(block.Difficult) + block.Nonce //从这里开始由al补齐：BPM) + block.PrevHash + strconv.Itoa(block.Difficult) + block.Nonce
	//生成hash对象
	hash0bject := sha256.New()
	//写入区块数据
	hash0bject.Write([]byte(blockData))
	//计算hash值
	hash := hash0bject.Sum(nil)

	//将hash值转换为16进制字符串
	return hex.EncodeToString(hash)
}

// 判断hash是否符合标准
func isHashValid(hash string, difficult int) bool {
	//根据难度定义判断条件
	prefix := strings.Repeat("0", difficult)
	//检查前缀中是否包含返回判断值
	return strings.HasPrefix(hash, prefix)
}

// 创建新的区块并挖矿
func generateBlock(oldBlock Block, BPM int) Block {
	var newBlock Block
	t := time.Now() //获取当前时间
	newBlock.Index = oldBlock.Index + 1
	newBlock.Timestamp = t.String()
	newBlock.BPM = BPM
	newBlock.PrevHash = oldBlock.Hash
	newBlock.Difficult = difficult
	for i := 0; ; i++ {
		hex := fmt.Sprintf("#{i}")
		newBlock.Nonce = hex
		if !isHashValid(calculateHash(newBlock), newBlock.Difficult) {
			fmt.Println(calculateHash(newBlock), "挖矿失败，请继续努力！")
			//定义间隔时间
			time.Sleep(time.Second * 1)
			continue
		} else {
			newBlock.Hash = calculateHash(newBlock)
			fmt.Println(newBlock.Hash, "挖矿成功！")
			break
		}

	}
	return newBlock
}

// 验证区块是否合法
func isBlockValid(newBlock, oldBlock Block) bool {
	//记录数据未递增---非法
	if newBlock.Index+1 != oldBlock.Index {
		return false
	}
	//前后hash值不相等---非法
	if oldBlock.Hash != newBlock.PrevHash {
		return false
	}
	//新区快的hash值不相等---非法
	if calculateHash(newBlock) != newBlock.Hash {
		return false
	}
	return true
}

// 配置web服务器
func run() error {
	mux := makeMuxRouter()
	httpAddr := os.Getenv("ADDR")
	log.Println("Listening on", os.Getenv("ADDR"))
	s := &http.Server{
		Addr:           ":" + httpAddr,
		Handler:        mux,
		ReadTimeout:    10 * time.Second,
		WriteTimeout:   10 * time.Second,
		MaxHeaderBytes: 1 << 20,
	}

	if err := s.ListenAndServe(); err != nil {
		return err
	}
	return nil
}

// 定义路由处理方式
func makeMuxRouter() http.Handler {
	makeMuxRouter := mux.NewRouter()
	//收到get请求获取区块链数据
	makeMuxRouter.HandleFunc("/", handleGetBlockChain).Methods("GET")
	//收到post请求创建新区块并挖矿
	makeMuxRouter.HandleFunc("/", handleWriteNewBlock).Methods("POST")
	return makeMuxRouter
}

// 获取所有区块的列表信息
func handleGetBlockChain(w http.ResponseWriter, r *http.Request) {
	//将区块链数据转换为json格式并返回
	bytes, err := json.MarshalIndent(Blockchain, "", "")
	//如果报错则返回错误信息封装到http错误对象中
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	io.WriteString(w, string(bytes))
}

// 创建新区块并挖矿
func handleWriteNewBlock(w http.ResponseWriter, r *http.Request) {
	//设置http响应头
	w.Header().Set("Content-Type", "application/json")
	var m Mwssage

	decoder := json.NewDecoder(r.Body)
	if err := decoder.Decode(&m); err != nil {
		respondWithJSON(w, r, http.StatusBadRequest, r.Body)
		return
	}
	defer r.Body.Close()

	//上锁
	mutex.Lock()
	newBlock := generateBlock(Blockchain[len(Blockchain)-1], m.BPM)
	//解锁
	mutex.Unlock()
	//判断创建的区块是否合法（标准）并打印
	if isBlockValid(newBlock, Blockchain[len(Blockchain)-1]) {
		Blockchain = append(Blockchain, newBlock)
		//详细信息打印
		spew.Dump(Blockchain)
	}
}

// 响应处理
func respondWithJSON(w http.ResponseWriter, r *http.Request, code int, playload interface{}) {
	//设置http响应头
	w.Header().Set("Content-Type", "application/json")
	//将数据转换为json格式并返回
	response, err := json.MarshalIndent(playload, "", "")
	if err != nil {
		w.WriteHeader(http.StatusInternalServerError)
		w.Write([]byte("http 500: Internal Server Error"))
		return
	}
	w.WriteHeader(code)
	w.Write(response)
}

// 启动程序
func main() {
	//加载.env文件中的参数(环境变量)
	err := godotenv.Load()
	if err != nil {
		log.Fatal(err)
	}

	go func() {
		t := time.Now()
		//声明创世区块
		genesisBlock := Block{}
		genesisBlock = Block{Index: 0, Timestamp: t.String(), BPM: 0, Hash: calculateHash(genesisBlock), PrevHash: "", Difficult: difficult, Nonce: ""} //该段从Nonce：0}开始均为al补齐
		spew.Dump(genesisBlock)

		//将创世区块添加进集合中
		mutex.Lock()
		Blockchain = append(Blockchain, genesisBlock)
		mutex.Unlock()
	}()
	//启动web服务器
	log.Fatal(run())
}
